# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: jaewon, inkyu

import logging
import os

from tornado.ioloop import PeriodicCallback

from coin.base.timestamp import get_timestamp
from coin.util.log_watcher.directory_watcher import DirectoryWatcher, DirectoryWatcherHandler


class LogWatcher(DirectoryWatcherHandler):
  _TailReader = None
  _recursive = False

  def __init__(self, target_dir, keepalive_mins=10, use_relative_path=False, log_callback=None):
    assert self._TailReader is not None

    self._target_dir = target_dir
    self._log_callback = log_callback
    self._readers = {}
    self._directory_watcher = DirectoryWatcher(target_dir=self._target_dir,
                                               handler=self,
                                               use_relative_path=use_relative_path,
                                               recursive=self._recursive)

    self._ioloop = None
    self._cleaner = None
    self._keepalive_ns = keepalive_mins * 60e9
    self._logger = logging.getLogger(__name__)

  def __del__(self):
    self.stop()

  def _clean(self):
    now_ts = get_timestamp()
    survived = {}

    for path, reader in self._readers.items():
      if (now_ts - reader.last_modified_ts) > self._keepalive_ns:
        self._logger.info('Detaching: %s', path)
        reader.close()
      else:
        survived[path] = reader
    self._readers = survived

    self._logger.info('* Workers:')
    for path, reader in self._readers.items():
      self._logger.info(' %s (%.2fm)', path, (now_ts - reader.last_modified_ts) / (60e9))

  def start(self, ioloop, clean_period_secs=10 * 60.):
    if self._ioloop is not None:
      raise NotImplementedError()
    self._ioloop = ioloop
    self._readers = {}
    self._directory_watcher.start(ioloop)
    self._cleaner = PeriodicCallback(self._clean, clean_period_secs * 1000)
    self._cleaner.start()

  def stop(self):
    if self._cleaner is not None:
      self._cleaner.stop()
      self._cleaner = None
    self._directory_watcher.stop()
    for reader in self._readers.values():
      reader.close()
    self._readers = {}

  def _add_reader(self, path, read_from_tail=True):
    assert self._ioloop is not None
    self._logger.info('Attaching: %s', path)
    self._readers[path] = self._TailReader(path,
                                           read_from_tail=read_from_tail,
                                           log_callback=self._log_callback,
                                           ioloop=self._ioloop)

  def handle_log_added(self, path):
    # Symlink is excluded by LogWatcher.
    path = os.path.abspath(path)

    if path not in self._readers:
      self._add_reader(path, False)

  def handle_log_removed(self, path):
    path = os.path.abspath(path)

    if path in self._readers:
      self._readers[path].close()
      del self._readers[path]

  def handle_log_modified(self, path):
    path = os.path.abspath(path)

    if path not in self._readers:
      self._add_reader(path)
    self._readers[path].handle_modified()

  def handle_unreachable(self, target_dir):
    pass
